# encoding: UTF-8
# 插件名称：图像转像素
# 作者：詹广权
# 版本：1.0.1
# 版权所有：http://www.cdsjcc.cn/xgj
# 说明：将图像文件转换为3D像素方块模型的SketchUp插件
#       支持PNG、JPG、JPEG、BMP、GIF等常见图像格式
#       可自定义方块尺寸、数量和总体模型大小
#       具备自适应比例计算功能和现代化进度显示界面
#       适用于像素艺术、建筑可视化和创意设计等场景
# 
# 图转像素插件 - 将图像转换为3D像素模型

# 加载扩展程序库
require 'extensions.rb'

# 创建扩展程序对象
tzxx_extension = SketchupExtension.new(
  '图像转像素',
  File.join(__dir__, 'tzxx.rb')
)

# 设置扩展程序属性
tzxx_extension.description = '将图像文件转换为3D像素方块模型，支持多种图像格式和自定义参数。'
tzxx_extension.version = '1.0.1'
tzxx_extension.creator = '詹广权'
tzxx_extension.copyright = '2024, http://www.cdsjcc.cn/xgj'

# 注册扩展程序
Sketchup.register_extension(tzxx_extension, true)

module Tzxx
  module ImageToPixels

    # 进度对话框实例变量
    @progress_dialog = nil

    # 确保只加载一次
    unless file_loaded?(__FILE__)
      # 添加菜单项到插件菜单
      plugins_menu = UI.menu('Plugins')
      plugins_menu.add_item('图像转像素') { self.convert_image_to_pixels }
      file_loaded(__FILE__)
    end

    # 创建进度对话框
    def self.create_progress_dialog
      return @progress_dialog if @progress_dialog && @progress_dialog.visible?
      
      # 对话框选项
      options = {
        :dialog_title => '图像转像素 - 处理进度',
        :scrollable => false,
        :resizable => false,
        :width => 450,
        :height => 300,
        :style => UI::HtmlDialog::STYLE_DIALOG
      }
      
      @progress_dialog = UI::HtmlDialog.new(options)
      
      # 设置HTML文件路径
      html_file = File.join(__dir__, 'tzxx', 'progress_dialog.html')
      @progress_dialog.set_file(html_file)
      
      # 添加回调函数
      @progress_dialog.add_action_callback('dialog_ready') do |action_context|
        puts "进度对话框已准备就绪"
      end
      
      # 居中显示
      @progress_dialog.center
      
      @progress_dialog
    end
    
    # 显示进度对话框
    def self.show_progress_dialog
      dialog = create_progress_dialog
      dialog.show unless dialog.visible?
      dialog.bring_to_front
      dialog
    end
    
    # 更新进度
    def self.update_progress(percent, current = nil, total = nil, status = nil, details = nil)
      return unless @progress_dialog && @progress_dialog.visible?
      
      script = "updateProgress(#{percent}"
      script += ", #{current}" if current
      script += ", #{total}" if total
      script += ", '#{status.to_s.gsub("'", "\\'")}'" if status
      script += ", '#{details.to_s.gsub("'", "\\'")}'" if details
      script += ");"
      
      @progress_dialog.execute_script(script)
    end
    
    # 显示错误
    def self.show_progress_error(message)
      return unless @progress_dialog && @progress_dialog.visible?
      
      script = "showError('#{message.to_s.gsub("'", "\\'")}')"
      @progress_dialog.execute_script(script)
    end
    
    # 显示完成
    def self.show_progress_complete(message, details = nil)
      return unless @progress_dialog && @progress_dialog.visible?
      
      script = "showComplete('#{message.to_s.gsub("'", "\\'")}'" 
      script += ", '#{details.to_s.gsub("'", "\\'")}'" if details
      script += ")"
      
      @progress_dialog.execute_script(script)
      
      # 3秒后自动关闭对话框
      UI.start_timer(3.0, false) {
        close_progress_dialog
      }
    end
    
    # 关闭进度对话框
    def self.close_progress_dialog
      if @progress_dialog && @progress_dialog.visible?
        @progress_dialog.close
      end
      @progress_dialog = nil
    end

    # 主功能方法：将图像转换为像素
    def self.convert_image_to_pixels
      model = Sketchup.active_model
      entities = model.active_entities
      materials = model.materials

      # 让用户选择图像文件
      image_path = UI.openpanel('选择图像文件', nil, '图像文件|*.png;*.jpg;*.jpeg;*.bmp;*.gif')
      return unless image_path && File.exist?(image_path)

      begin
        # 加载图像获取尺寸信息
        image_rep = Sketchup::ImageRep.new
        image_rep.load_file(image_path)
        image_width = image_rep.width
        image_height = image_rep.height
        
        # 让用户设置方块尺寸
        pixel_dimensions = get_pixel_dimensions(image_width, image_height)
        return unless pixel_dimensions
      rescue => e
        UI.messagebox("无法读取图像：#{e.message}")
        return
      end
      
      pixel_width = pixel_dimensions[:width]
      pixel_height = pixel_dimensions[:height]
      pixel_depth = pixel_dimensions[:depth]

      begin
        # 使用用户设置的方块参数
        blocks_x = pixel_dimensions[:blocks_x]  # 长方向方块个数
        blocks_y = pixel_dimensions[:blocks_y]  # 宽方向方块个数
        total_blocks = blocks_x * blocks_y

        # 检查方块数量，防止创建过多几何体
         if total_blocks > 250000 # 限制最大方块数量
           result = UI.messagebox("方块数量较大(#{blocks_x}x#{blocks_y}=#{total_blocks}个)，可能导致SketchUp卡顿。\n建议减少方块数量。\n\n是否继续？", MB_YESNO)
           return if result == IDNO
         end

        # 设置方块尺寸和间距
        pixel_spacing = 0.mm
        total_size_x = pixel_width + pixel_spacing
        total_size_y = pixel_height + pixel_spacing

        # 获取所有像素颜色
        colors = image_rep.colors
        unless colors
          UI.messagebox("无法读取图像颜色数据")
          return
        end

        # 显示进度对话框
        show_progress_dialog
        
        # 等待对话框加载完成
        UI.start_timer(0.5, false) {
          # 开始操作
          model.start_operation('图像转像素', true)
          
          # 更新初始进度
          update_progress(0, 0, total_blocks, "开始处理图像转方块", "方块数量: #{blocks_x}x#{blocks_y}, 尺寸: #{(pixel_width/1.mm).round(1)}x#{(pixel_height/1.mm).round(1)}x#{(pixel_depth/1.mm).round(1)}mm")
          
          # 开始实际处理
          process_image_blocks(model, entities, materials, image_rep, pixel_dimensions, colors)
        }
        
        return # 提前返回，避免重复执行后续代码
      rescue => e
        show_progress_error("图像转方块失败: #{e.message}")
        UI.messagebox("无法读取图像：#{e.message}")
        return
      end
    end
    
    # 处理图像方块的主要逻辑
    def self.process_image_blocks(model, entities, materials, image_rep, pixel_dimensions, colors)
      begin
        pixel_width = pixel_dimensions[:width]
        pixel_height = pixel_dimensions[:height]
        pixel_depth = pixel_dimensions[:depth]
        blocks_x = pixel_dimensions[:blocks_x]
        blocks_y = pixel_dimensions[:blocks_y]
        total_blocks = blocks_x * blocks_y
        
        # 设置方块尺寸和间距
        pixel_spacing = 0.mm
        total_size_x = pixel_width + pixel_spacing
        total_size_y = pixel_height + pixel_spacing
        
        # 获取图像尺寸
        image_width = image_rep.width
        image_height = image_rep.height

        # 获取模型中心位置
        center = Geom::Point3d.new(0, 0, 0)
        start_x = center.x - (blocks_x * total_size_x) / 2
        start_y = center.y - (blocks_y * total_size_y) / 2

        # 创建材质缓存，避免重复创建相同颜色的材质
        material_cache = {}
        processed_blocks = 0
        pixel_groups = [] # 存储所有方块组的数组

        # 读取并处理每个方块，每个方块创建为独立的组
        (0...blocks_y).each do |y|
          (0...blocks_x).each do |x|
            # 计算对应的图像像素位置（采样映射）
            img_x = (x.to_f / blocks_x * image_width).round
            img_y = (y.to_f / blocks_y * image_height).round
            
            # 确保索引在有效范围内
            img_x = [0, [img_x, image_width - 1].min].max
            img_y = [0, [img_y, image_height - 1].min].max
            
            # 获取对应像素颜色
            index = img_y * image_width + img_x
            pixel_color = colors[index]
            next if pixel_color.alpha == 0 # 跳过透明像素

            # 使用颜色值作为材质缓存键，避免创建重复材质
            color_key = "#{pixel_color.red}_#{pixel_color.green}_#{pixel_color.blue}_#{pixel_color.alpha}"
            material = material_cache[color_key]
            unless material
              material_name = "pixel_color_#{material_cache.size}"
              material = materials.add(material_name)
              material.color = pixel_color
              material_cache[color_key] = material
            end

            # 计算像素位置
            pixel_x = start_x + x * total_size_x
            pixel_y = start_y + y * total_size_y
            pixel_z = center.z

            # 为每个方块创建独立的组
            pixel_group = entities.add_group
            pixel_group.name = "Block_#{x}_#{y}"
            pixel_group_entities = pixel_group.entities

            # 在组内使用EntitiesBuilder创建立方体
            pixel_group_entities.build do |builder|
              # 底面 (法线向下)
              bottom_points = [
                [pixel_x, pixel_y, pixel_z],
                [pixel_x, pixel_y + pixel_height, pixel_z],
                [pixel_x + pixel_width, pixel_y + pixel_height, pixel_z],
                [pixel_x + pixel_width, pixel_y, pixel_z]
              ]
              bottom_face = builder.add_face(bottom_points)
              if bottom_face
                bottom_face.material = material
                bottom_face.back_material = material
              end

              # 顶面 (法线向上)
              top_points = [
                [pixel_x, pixel_y, pixel_z + pixel_depth],
                [pixel_x + pixel_width, pixel_y, pixel_z + pixel_depth],
                [pixel_x + pixel_width, pixel_y + pixel_height, pixel_z + pixel_depth],
                [pixel_x, pixel_y + pixel_height, pixel_z + pixel_depth]
              ]
              top_face = builder.add_face(top_points)
              if top_face
                top_face.material = material
                top_face.back_material = material
              end

              # 前面 (法线向前)
              front_points = [
                [pixel_x, pixel_y, pixel_z],
                [pixel_x + pixel_width, pixel_y, pixel_z],
                [pixel_x + pixel_width, pixel_y, pixel_z + pixel_depth],
                [pixel_x, pixel_y, pixel_z + pixel_depth]
              ]
              front_face = builder.add_face(front_points)
              if front_face
                front_face.material = material
                front_face.back_material = material
              end

              # 后面 (法线向后)
              back_points = [
                [pixel_x + pixel_width, pixel_y + pixel_height, pixel_z],
                [pixel_x, pixel_y + pixel_height, pixel_z],
                [pixel_x, pixel_y + pixel_height, pixel_z + pixel_depth],
                [pixel_x + pixel_width, pixel_y + pixel_height, pixel_z + pixel_depth]
              ]
              back_face = builder.add_face(back_points)
              if back_face
                back_face.material = material
                back_face.back_material = material
              end

              # 左面 (法线向左)
              left_points = [
                [pixel_x, pixel_y + pixel_height, pixel_z],
                [pixel_x, pixel_y, pixel_z],
                [pixel_x, pixel_y, pixel_z + pixel_depth],
                [pixel_x, pixel_y + pixel_height, pixel_z + pixel_depth]
              ]
              left_face = builder.add_face(left_points)
              if left_face
                left_face.material = material
                left_face.back_material = material
              end

              # 右面 (法线向右)
              right_points = [
                [pixel_x + pixel_width, pixel_y, pixel_z],
                [pixel_x + pixel_width, pixel_y + pixel_height, pixel_z],
                [pixel_x + pixel_width, pixel_y + pixel_height, pixel_z + pixel_depth],
                [pixel_x + pixel_width, pixel_y, pixel_z + pixel_depth]
              ]
              right_face = builder.add_face(right_points)
              if right_face
                right_face.material = material
                right_face.back_material = material
              end
            end

            # 将方块组添加到数组中
            pixel_groups << pixel_group

            processed_blocks += 1
            # 每处理50个方块显示一次进度（更频繁的更新）
            if processed_blocks % 50 == 0
              progress_percent = (processed_blocks.to_f/total_blocks*100).round(1)
              status_text = "正在创建方块"
              update_progress(progress_percent, processed_blocks, total_blocks, status_text)
              puts "正在处理方块: #{processed_blocks}/#{total_blocks} (#{progress_percent}%)"  # 同时在控制台输出
            end
          end
        end

        # 将所有方块组打包到一个主组中
        if pixel_groups.size > 0
          update_progress(95, processed_blocks, total_blocks, "正在打包方块组")
          main_group = entities.add_group(pixel_groups)
          main_group.name = "ImageBlocks_#{blocks_x}x#{blocks_y}"
          puts "已将#{pixel_groups.size}个方块组打包到主组中"
        end

        model.commit_operation
        
        # 显示完成信息
        completion_text = "图像转方块完成!"
        completion_details = "方块数量: #{blocks_x}x#{blocks_y}, 材质种类: #{material_cache.size}, 模型尺寸: #{(pixel_dimensions[:total_length]/1.mm).round(1)}x#{(pixel_dimensions[:total_width]/1.mm).round(1)}mm"
        show_progress_complete(completion_text, completion_details)
        puts "成功将图像转换为方块模型，方块数量: #{blocks_x}x#{blocks_y}，使用了#{material_cache.size}种材质"
        
        UI.messagebox("图像转方块成功！\n方块数量: #{blocks_x}x#{blocks_y}\n总模型尺寸: #{(pixel_dimensions[:total_length]/1.mm).round(1)}x#{(pixel_dimensions[:total_width]/1.mm).round(1)}mm\n使用了#{material_cache.size}种材质")

      rescue => e
        model.abort_operation rescue nil
        error_text = "图像转方块失败: #{e.message}"
        show_progress_error(error_text)
        puts error_text
        puts e.backtrace
        
        UI.messagebox("转换失败: #{e.message}")
        
        # 3秒后关闭进度对话框
        UI.start_timer(3.0, false) {
          close_progress_dialog
        }
      end
    end

    # 获取用户设置的像素方块参数
    def self.get_pixel_dimensions(image_width, image_height)
      # 设置默认值
      default_blocks_x = image_width.to_s  # 长方向方块个数，默认为图像宽度
      default_blocks_y = image_height.to_s # 宽方向方块个数，默认为图像高度
      default_block_height = "10"          # 方块高度(mm)
      default_total_length = "1000"        # 总长度约束(mm)
      default_total_width = "0"            # 总宽度约束(mm)，0表示自动比例
      
      # 创建输入对话框
      prompts = [
        "长：多少排方块 (个):", 
        "宽：多少排方块 (个):", 
        "高度：方块的高度 (mm):",
        "总尺寸 - 长：约束长度 (mm):",
        "总尺寸 - 宽：约束宽度 (mm):"
      ]
      defaults = [default_blocks_x, default_blocks_y, default_block_height, default_total_length, default_total_width]
      list = ["", "", "", "", ""]
      
      # 构建对话框标题
      title = "设置像素方块参数\n\n" +
              "图像尺寸: #{image_width} x #{image_height} 像素\n" +
              "说明：\n" +
              "• 方块个数：设置长宽方向的方块数量\n" +
              "• 自适应功能：当长或宽其中一项设为0时，将根据图像比例自动计算\n" +
              "• 总尺寸约束：限制整体模型的最大尺寸\n" +
              "• 当总宽度设为0时，将按比例自动计算\n" +
              "• 系统会自动计算每个方块的实际尺寸"
      
      input = UI.inputbox(prompts, defaults, list, title)
      return nil unless input
      
      begin
        # 解析用户输入
        blocks_x = input[0].to_i      # 长方向方块个数
        blocks_y = input[1].to_i      # 宽方向方块个数
        block_height = input[2].to_f.mm  # 方块高度
        total_length = input[3].to_f.mm  # 总长度约束
        total_width = input[4].to_f.mm   # 总宽度约束
        
        # 验证基本输入值
        if block_height <= 0
          UI.messagebox("方块高度必须大于0！")
          return nil
        end
        
        if total_length <= 0
          UI.messagebox("总长度约束必须大于0！")
          return nil
        end
        
        # 处理自适应逻辑：当一项为0时自动计算
        if blocks_x == 0 && blocks_y == 0
          UI.messagebox("长宽方向的方块个数不能同时为0！")
          return nil
        elsif blocks_x == 0
          # 长方向自适应：根据宽方向和图像比例计算
          if blocks_y <= 0
            UI.messagebox("宽方向方块个数必须大于0！")
            return nil
          end
          aspect_ratio = image_width.to_f / image_height.to_f
          if total_width > 0
            # 有总宽度约束，根据比例计算长方向方块数
            blocks_x = (blocks_y * aspect_ratio * total_length / total_width).round
          else
            # 无总宽度约束，按图像比例计算
            blocks_x = (blocks_y * aspect_ratio).round
          end
          blocks_x = [1, blocks_x].max  # 确保至少为1
        elsif blocks_y == 0
          # 宽方向自适应：根据长方向和图像比例计算
          if blocks_x <= 0
            UI.messagebox("长方向方块个数必须大于0！")
            return nil
          end
          aspect_ratio = image_height.to_f / image_width.to_f
          if total_width > 0
            # 有总宽度约束，根据比例计算宽方向方块数
            blocks_y = (blocks_x * aspect_ratio * total_width / total_length).round
          else
            # 无总宽度约束，按图像比例计算
            blocks_y = (blocks_x * aspect_ratio).round
          end
          blocks_y = [1, blocks_y].max  # 确保至少为1
        else
          # 两项都不为0，验证输入值
          if blocks_x <= 0 || blocks_y <= 0
            UI.messagebox("方块个数必须大于0！")
            return nil
          end
        end
        
        # 计算每个方块的实际尺寸
        block_width = total_length / blocks_x   # 每个方块的长度
        
        if total_width > 0
          # 用户指定了总宽度约束
          block_depth = total_width / blocks_y  # 每个方块的宽度
        else
          # 自动按比例计算总宽度
          aspect_ratio = image_height.to_f / image_width.to_f
          calculated_total_width = total_length * aspect_ratio
          block_depth = calculated_total_width / blocks_y
        end
        
        # 验证计算结果
        if block_width <= 0 || block_depth <= 0
          UI.messagebox("计算出的方块尺寸无效，请检查输入参数！")
          return nil
        end
        
        # 显示计算结果给用户确认
        actual_total_width = block_depth * blocks_y
        actual_total_length = block_width * blocks_x
        
        # 构建确认消息，包含自适应计算信息
        confirm_msg = "计算结果：\n\n"
        
        # 检查是否使用了自适应计算
        if input[0].to_i == 0
          confirm_msg += "🔄 长方向自适应计算：#{blocks_x} 个方块\n"
        end
        if input[1].to_i == 0
          confirm_msg += "🔄 宽方向自适应计算：#{blocks_y} 个方块\n"
        end
        
        confirm_msg += "方块数量：#{blocks_x} x #{blocks_y} = #{blocks_x * blocks_y} 个\n" +
                      "每个方块尺寸：\n" +
                      "  长度：#{(block_width/1.mm).round(2)} mm\n" +
                      "  宽度：#{(block_depth/1.mm).round(2)} mm\n" +
                      "  高度：#{(block_height/1.mm).round(2)} mm\n\n" +
                      "总模型尺寸：\n" +
                      "  长度：#{(actual_total_length/1.mm).round(2)} mm\n" +
                      "  宽度：#{(actual_total_width/1.mm).round(2)} mm\n" +
                      "  高度：#{(block_height/1.mm).round(2)} mm\n\n" +
                      "是否继续创建？"
        
        result = UI.messagebox(confirm_msg, MB_YESNO)
        return nil if result == IDNO
        
        return {
          width: block_width,
          height: block_depth,
          depth: block_height,
          blocks_x: blocks_x,
          blocks_y: blocks_y,
          total_length: actual_total_length,
          total_width: actual_total_width
        }
        
      rescue => e
        UI.messagebox("输入格式错误：#{e.message}")
        return nil
      end
    end

  end # module ImageToPixels
end # module Tzxx